/*
 * Bunisoft the Open Source Communications Company
 * Copyright 2006, Bunisoft Inc.,
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.teremail.panto;

import java.io.IOException;
import java.io.InputStream;
import java.io.PushbackInputStream;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

/**
 * Parses the header of a message or body part.
 * 
 * @author Michael Barker
 *
 */
public class HeaderParser {

    public BodyHeader parseHeader(ContentHandler handler, 
            ExIterator<String,IOException> exi, ContentType defaultContentType) 
            throws IOException {

        handler.startHeader();
        BodyHeader bodyHeader = new BodyHeader();
        bodyHeader.setContentType(defaultContentType);

        StringBuilder sb = new StringBuilder();

        StringBuilder mh = null;

        while (exi.hasNext()) {
            String s = exi.next();
            if (s.startsWith(" ") || s.startsWith("\t")) {
                if (mh == null) {
                    mh = new StringBuilder();
                } else {
                    mh.append("\r\n");    
                }
                mh.append(s);
            } else {
                // Parse the previous header.
                if (mh != null) {
                    String line = mh.toString();
                    addContentHeader(bodyHeader, line);
                    handler.field(line);
                }
                mh = new StringBuilder(s);
            }
            sb.delete(0, sb.length());
        }

        if (mh != null) {
            String line = mh.toString();
            addContentHeader(bodyHeader, line);
            handler.field(line);
        }
        
        handler.endHeader();
        return bodyHeader;
    }
    
    public BodyHeader parseHeader(ContentHandler handler, PushbackInputStream in, 
            ContentType defaultContentType) throws IOException {

        IOStreamIterator exi = new IOStreamIterator(in);
        return parseHeader(handler , exi, defaultContentType);
    }    
    
    public BodyHeader parseHeader(String header, 
            ContentType defaultContentType) throws IOException {
        ContentHandler ch = new NullContentHandler();
        List<String> fields = Arrays.asList(header.split("\r\n"));
        IteratorIterator<String> exi = new IteratorIterator<String>(fields.iterator());
        return parseHeader(ch, exi, defaultContentType);
        
    }

    private void addContentHeader(BodyHeader header, String line) {
        
        try {
            ContentType ct = ContentType.parse(line);
            if (ct != null) {
                header.setContentType(ct);
            }
        } catch (ParseException e) {
            System.err.println("Content Type: " + e.getMessage());
        }
        try {
            ContentDisposition cd = ContentDisposition.parse(line);
            if (cd != null) {
                header.setContentDisposition(cd);
            }
        } catch (ParseException e) {
            System.err.println("Content Disposition: " + e.getMessage());
        }
        try {
            ContentTransferEncoding cte = ContentTransferEncoding.parse(line);
            if (cte != null ){
                header.setContentTransferEncoding(cte);
            }
        } catch (ParseException e) {
            System.err.println("Content Transfer Encoding: " + e.getMessage());
        }
    }
        
    private static class NullContentHandler implements ContentHandler {

        public void body(BodyHeader header, InputStream is) throws IOException {
        }

        public void endBodyPart() {
        }

        public void endHeader() {
        }

        public void endMessage() {
        }

        public void endMultipart() {
        }

        public void epilogue(InputStream in) throws IOException {
        }

        public void field(String fieldData) {
        }

        public void preamble(InputStream in) throws IOException {
        }

        public void startBodyPart() {
        }

        public void startHeader() {
        }

        public void startMessage() {
        }

        public void startMultipart(BodyHeader header) {
        }        
    }
    
    private interface ExIterator<T, E extends Exception> {
        public boolean hasNext() throws E;
        public T next() throws E;
    }
    
    private static class IteratorIterator<S> implements ExIterator<S, IOException> {

        private Iterator<S> iter;

        public IteratorIterator(Iterator<S> iter) {
            this.iter = iter;
        }
        
        public boolean hasNext() throws IOException {
            return iter.hasNext();
        }

        public S next() throws IOException {
            return iter.next();
        }
    }
    
    private static class IOStreamIterator implements ExIterator<String, IOException> {
        
        private StringBuilder sb = new StringBuilder();
        private final static String CHARSET = "US-ASCII";
        private PushbackInputStream pIn;
        
        public IOStreamIterator(PushbackInputStream pIn) throws IOException {
            this.pIn = pIn;
        }

        public boolean hasNext() throws IOException {
            sb.delete(0, sb.length());
            IOUtil.appendLine(sb, pIn, CHARSET);
            return sb.toString().length() > 0;
        }

        public String next() throws IOException {
            return sb.toString();
        }
        
    }
}
